package handler

import (
	redisLayer "filestore-rebuild/cache/redis"
	"filestore-rebuild/db"
	"filestore-rebuild/util"
	"filestore-rebuild/util/jwtUtil"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/gin-gonic/gin"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

// MultipartUploadInfo 分块上传信息
type MultipartUploadInfo struct {
	FileHash   string
	FileSize   int
	UploadId   string
	ChunkSize  int
	ChunkCount int
}

// InitialMultipartUploadHandler 初始化分块上传接口
func InitialMultipartUploadHandler(ctx *gin.Context) {
	// 1.解析参数
	token := ctx.GetHeader("Token")
	claims, _ := jwtUtil.ParseToken(token)
	username := claims.UserID
	// username := "admin"
	filehash := ctx.Request.FormValue("filehash")
	filesize, err := strconv.Atoi(ctx.Request.FormValue("filesize"))
	if err != nil {
		ctx.JSON(http.StatusOK, util.Resp{Code: -1, Msg: "Invalid param"})
		return
	}

	// 2.获取redis连接
	redisConn := redisLayer.Pool().Get()
	defer redisConn.Close()

	// 3.生成分块上传初始化信息
	uploadInfo := MultipartUploadInfo{
		FileHash:   filehash,
		FileSize:   filesize,
		UploadId:   username + fmt.Sprintf("%x", time.Now().Unix()),
		ChunkSize:  5 * 1024 * 1024,
		ChunkCount: int(math.Ceil(float64(filesize) / (5 * 1024 * 1024))),
	}
	// 4.将初始化信息写入redis
	redisConn.Do("HSET", "MP_"+uploadInfo.UploadId, "chunkcount", uploadInfo.ChunkCount)
	redisConn.Do("HSET", "MP_"+uploadInfo.UploadId, "filehash", uploadInfo.FileHash)
	redisConn.Do("HSET", "MP_"+uploadInfo.UploadId, "filesize", uploadInfo.FileSize)

	// 5.将初始化信息响应给客户端
	ctx.JSON(http.StatusOK, util.Resp{
		Code: 1,
		Msg:  "upload init success",
		Data: uploadInfo,
	})
}

// UploadPartHandler 上传分块接口
func UploadPartHandler(ctx *gin.Context) {
	// 1.解析参数
	uploadID := ctx.Request.FormValue("uploadid")
	chunkIndex := ctx.Request.FormValue("index")
	// 2.获取redis连接
	redisConn := redisLayer.Pool().Get()
	defer redisConn.Close()
	// 3.获取文件句柄，存储文件分块内容
	fpath := "/tmp/data/" + uploadID + "/" + chunkIndex
	os.MkdirAll(path.Dir(fpath), 0744)
	fd, err := os.Create(fpath)
	if err != nil {
		ctx.JSON(http.StatusOK, util.Resp{Code: -1, Msg: "Server error"})
		return
	}
	defer fd.Close()

	buffer := make([]byte, 1024*1024)
	for {
		read, err := ctx.Request.Body.Read(buffer)
		fd.Write(buffer[:read])
		if err != nil {
			break
		}
	}

	// 4.更新redis缓存
	redisConn.Do("HSET", "MP_"+uploadID, "chkidx_"+chunkIndex, 1)

	// 5.返回处理结果给客户端
	ctx.JSON(http.StatusOK, util.Resp{Code: 1, Msg: "Success"})
}

// CompleteUploadHandler 完成分块上传并合并分块接口
func CompleteUploadHandler(c *gin.Context) {
	// 1.解析参数
	token := c.GetHeader("Token")
	claims, _ := jwtUtil.ParseToken(token)
	username := claims.UserID
	// username := "admin"

	uploadID := c.Request.FormValue("uploadid")
	fileHash := c.Request.FormValue("filehash")
	fileName := c.Request.FormValue("filename")
	fileSize, err := strconv.ParseInt(c.Request.FormValue("filesize"), 10, 64)
	if err != nil {
		c.JSON(http.StatusOK, util.Resp{Code: -1, Msg: "Invalid param filesize"})
		return
	}
	// 2.获得redis连接池连接
	redisConn := redisLayer.Pool().Get()

	// 3.通过uploadID查询redis并判断是否所有块分块上传完成
	data, err := redis.Values(redisConn.Do("HGETALL", "MP_"+uploadID))
	if err != nil {
		log.Printf("query upload info from redis error: %s\n", err.Error())
		c.JSON(http.StatusOK, util.Resp{Code: -1, Msg: "Server error"})
		return
	}

	totalCount := 0
	chunkCount := 0
	for i := 0; i < len(data); i += 2 {
		k := string(data[i].([]byte))
		v := string(data[i+1].([]byte))
		if k == "chunkcount" {
			totalCount, err = strconv.Atoi(v)
			if err != nil {
				log.Printf("redis totalCount convert error: %s\n", err.Error())
				c.JSON(http.StatusOK, util.Resp{Code: -1, Msg: "Server error"})
				return
			}
		} else if strings.HasPrefix(k, "chkidx_") && v == "1" {
			chunkCount++
		}
	}

	if totalCount != chunkCount {
		log.Printf("totalConut[%d] not equal chunkCount[%d]\n", totalCount, chunkCount)
		c.JSON(http.StatusOK, util.Resp{Code: -1, Msg: "Server error"})
		return
	}

	// 4.合并分块，存储
	dirPath := "/tmp/data/" + uploadID + "/"
	files, err := os.ReadDir(dirPath)
	if err != nil {
		log.Printf("Open upload bolck dir failed: %s\n", err.Error())
		c.JSON(http.StatusOK, util.Resp{Code: -1, Msg: "Server error"})
		return
	}
	complateFile, err := os.Create("/tmp/data/" + fileHash)
	defer complateFile.Close()
	if err != nil {
		log.Printf("Create file '%s' error: %s\n", fileHash, err.Error())
		c.JSON(http.StatusOK, util.Resp{Code: -1, Msg: "Server error"})
		return
	}
	for _, file := range files {
		if file.Name() == ".DS_Store" {
			continue
		}
		fileBuffer, err := ioutil.ReadFile("/tmp/data/" + uploadID + "/" + file.Name())
		if err != nil {
			fmt.Printf("Open file '%s' error: %s\n", file.Name(), err.Error())
			c.JSON(http.StatusOK, util.Resp{Code: -1, Msg: "Server error"})
			return
		}
		complateFile.Write(fileBuffer)
	}

	// TODO 清理中间缓存
	err = os.RemoveAll("/tmp/data/" + uploadID)
	if err != nil {
		log.Printf("remove part failed: %s\n", err.Error())
	}
	// TODO 删除redis缓存
	_, err = redisConn.Do("DEL", "MP_"+uploadID, "*")
	if err != nil {
		log.Printf("del redis key error: %s\n", err.Error())
	}
	// 5.更新唯一文件表和用户文件表
	db.UploadFile(fileHash, fileName, fileSize, "/tmp/data/"+fileHash)
	db.InsertUserFile(username, fileHash, fileSize, fileName)
	//  6.响应处理结果
	c.JSON(http.StatusOK, util.Resp{Code: 1, Msg: "Success"})
}

// CancelUploadPartHandler 取消分块上传接口
func CancelUploadPartHandler(c *gin.Context) {
	// TODO 1.删除已经上传的文件块

	// TODO 2.删除redis缓存

	// TODO 3.更新mysql文件status
}

// MultipartUploadStatusHandler 检查分块上传状态接口
func MultipartUploadStatusHandler(c *gin.Context) {
	// TODO 1.检查分块上传是否有效

	// TODO 2.获得分块初始化信息

	// TODO 3.获取已上传的分块信息

}
